package org.jsequences.io;

import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.CharBuffer;
import com.google.common.io.CharSink;
import com.google.common.io.CharSource;
import com.google.common.io.CharStreams;

/**
 * A CharSink implementation which stores the written data internally into a <code>Char[]</code>.
 * This class behaves pretty much like a CharArrayOutputStream and can be used for the
 * same purpose: To build a resource in memory in a first step and read it in in a second.
 * In this case, the resource to build is a CharSource out of a CharSink. Both Sink and Source are
 * "ResourceManagers" for Streams, much like an Iterable is a ResourceManager for an
 * Iterator.
 * <p>
 * After writing with the methods of Sink, the CharArraySink provides multiple methods to obtain the
 * written data:
 * <ol>
 * <li>Via toString()/toCharArray()/toCharSource(): These create truncated copies of the written
 * data which will be independent of this Sink.</li>
 * <li>Via asCharBuffer()/asCharSource(): These create cheap views of the written data which will be
 * sparse (they leak memory). Since views are not independent, write()-Operations may or may not
 * write through to them, see the appropiate javadocs. Try to use them only if you are sure that the
 * sink cannot be changed afterwards!</li>
 * </ol>
 * <p>
 * <b>Performance notes:</b> Handling Streams in memory always yields the risk of OutOfMemory-Errors
 * if the size is unbounded. Therefore, use this class only in scenarios where you are sure the size
 * won't expand beyond limits in the lower megaChar-Range. The big upside of in-Memory-Stream
 * handling however is that it needs absolutely <b>no</b> kind of cleanup and is usually very fast.
 */
public final class CharArraySink extends CharSink {

    private ViewableCharArrayWriter out;

    /**
     * Creates a new ByteArraySink with the default initial capacity.
     */
    public CharArraySink() {
        this(64);
    }

    /**
     * Creates a new ByteArraySink, with the specified initial capacity.
     * 
     * @param capacity the initial capacity.
     * @exception IllegalArgumentException if size is negative.
     */
    public CharArraySink(int capacity) {
        out = new ViewableCharArrayWriter(capacity);
    }

    /**
     * Returns the stream to which all data should be written.
     * This method will always return the same stream as long as the data is not consumed.
     */
    @Override
    public Writer openStream() {
        return out;
    }

    /**
     * Returns the written data as a String.
     */
    @Override
    public String toString() {
        return out.toString();
    }

    /**
     * Allocates a <i>new</i> array and copies all written Chars into it.
     * 
     * @return the current contents of this sink, as a Char array.
     */
    public char[] toCharArray() {
        return out.toCharArray();
    }

    /**
     * Creates a <i>new</i> CharSource and copies a written Chars into it.
     * Later changes to the sink are not reflected.
     * <p>
     * This method should be used whenever a 'safe' and independent copy should be created, for
     * example to store it into a bean or as a return value of a public method. If you just need a
     * 'fast' view which does not involve a copy of the internal data and potentially leaks memory,
     * then consider using asCharSource() instead.
     */
    public CharSource toCharSource() {
        return CharStreams.asCharSource(toString());
    }

    /**
     * Returns a readonly buffer view which represents a snapshot of the <b>current</b>
     * data contained in this sink. Usually the view will be sparse and leaks memory,
     * since the internal structures are on average 50% larger than needed. The capacity of
     * the buffer is undefined: its size may only be queried with the limit()-method.
     * <p>
     * <b>Warning</b>: Multiple methods of this class have an undefined behavior: After a write() or
     * reset() operation on the sink, the contents of the buffer are completely <i>undefined</i>! As
     * well, the capacity of the buffer is undefined. Therefore, all methods whic Therefore, this
     * method should be used sparingly and with great care!
     * <p>
     * This method exists just to interoperate smoothly with java's NIO-library. If you just need
     * safe IO view of the internal data then <b>use asCharSource()</b>, which has fewer pitfalls
     * and is easier to use!
     */
    public CharBuffer asCharBuffer() {
        return out.asCharBuffer();
    }

    /**
     * Views the currently written data as a readonly CharSource. Usually the view will be sparse
     * and leaks memory, since the internal structures are on average 50% larger than needed.
     * Changes to the underlying sink are reflected to the source.
     * <p>
     * <b>Warning</b>: Reader of the CharSource behaves in the same way as the asCharBuffer() view:
     * The data read will be undefined if the sink is modified will it is processed.
     */
    public CharSource asCharSource() {
        return out.asCharSource();
    }

    /**
     * Drops all data which has been written to this sink and frees up the used memory.
     * Afterwards, the sink behaves as if it was just created.
     */
    public void reset() {
        out.reset();
    }
}

/**
 * An Extension of the CharArrayOutputStream which allows it to read the contents of
 * the Stream with readonly-views.
 * 
 * @author Eric Giese
 */
final class ViewableCharArrayWriter extends CharArrayWriter {

    public ViewableCharArrayWriter(int capacity) {
        super(capacity);
    }

    /**
     * Views the Stream as a readonly buffer.
     */
    public CharBuffer asCharBuffer() {
        return CharBuffer.wrap(buf, 0, count).asReadOnlyBuffer();
    }

    /**
     * Views the Stream as a CharSource.
     */
    public CharSource asCharSource() {
        return new AsCharSource();
    }

    private final class AsCharSource extends CharSource {

        @Override
        public Reader openStream() throws IOException {
            return new CharArrayReader(buf, 0, count);
        }

        @Override
        public String read() throws IOException {
            return ViewableCharArrayWriter.this.toString();
        }

        @Override
        public String toString() {
            int max = 15;
            String seq = count <= max ? new String(buf, 0, count) :
                    new String(buf, 0, max - 3) + "...";
            return "CharArrayOutputStream.asCharSource(" + seq + ')';
        }
    }
}
